﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using Sun.Wadl;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Xml;

namespace Hbrs.Soa.Restclient.CodeToWadl
{
    public class CodeToWadlGenerator
    {
        private List<Type> GetTypesFromAssembly(Assembly asm)
        {
            List<Type> contractType = new List<Type>();

            Type[] asmtypes = asm.GetTypes();

            foreach (Type type in asmtypes)
            {
                object[] attributes = type.GetCustomAttributes(typeof(ServiceContractAttribute), true);
                if (attributes.Length == 0)
                {

                    Type[] interfaces = type.GetInterfaces();
                    for (int i = 0; i < interfaces.Length; i++)
                    {
                        Type inf = interfaces[i];
                        attributes = inf.GetCustomAttributes(typeof(ServiceContractAttribute), true);

                        if (attributes.Length > 0)
                        {
                            contractType.Add(inf);
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < attributes.Length; i++)
                    {
                        contractType.Add(attributes[i].GetType());

                    }
                }
            }


            if (contractType.Count != 0)
            {
                return contractType;
            }
            else
            {
                return null;
            }
        }


        private method FindParameters(MethodInfo info)
        {
            method m = new method();
            m.id = info.Name;

            ParameterInfo[] parameters = info.GetParameters();


            m.request = new request();
            m.request.param = new param[parameters.Length];

            for (int j = 0; j < m.request.param.Length; j++)
            {
                m.request.param[j] = new param();
            }


            for (int j = 0; j < parameters.Length; j++)
            {

                String name = "";
                String ns = null;

                switch (parameters[j].ParameterType.FullName)
                {
                    case ("System.String"):
                        {
                            name = "String";
                            ns = "http://www.w3.org/2001/XMLSchema";
                            break;
                        }
                    case ("System.Int"):
                        {
                            name = "Int";
                            ns = "http://www.w3.org/2001/XMLSchema";
                            break;
                        }
                    default:
                        {
                            name = parameters[j].ParameterType.FullName;
                            break;
                        }
                }

                XmlQualifiedName type = new XmlQualifiedName(name, ns);

                m.request.param[j].type = type;


                m.request.param[j].name = parameters[j].Name;
                m.request.param[j].required = !parameters[j].IsOptional;
                m.request.param[j].style = ParamStyle.plain;
                m.request.param[j].styleSpecified = true;
            }


            ParameterInfo ResponseParameters = info.ReturnParameter;

            if (ResponseParameters.ParameterType.FullName != "System.Void")
            {
                string rname = ResponseParameters.ParameterType.FullName;

                m.response = new response[1];
                m.response[0] = new response();
                m.response[0].param = new param[1];
                m.response[0].param[0] = new param();

                m.response[0].param[0].name = "value";

                Type enumType = ResponseParameters.ParameterType.GetInterface(typeof(System.Collections.IEnumerable).Name);
                if (enumType != null)
                {
                    m.response[0].param[0].repeating = true;
                }

                if (ResponseParameters.ParameterType.IsGenericType == true)
                {
                    Type[] type = ResponseParameters.ParameterType.GetGenericArguments();
                    rname = type[0].FullName;



                }
                XmlQualifiedName rtype = new XmlQualifiedName(rname);
                m.response[0].param[0].type = rtype;

                string presentation = GetMethodPresentation(info);
                m.response[0].representation = new representation[1];
                m.response[0].representation[0] = new representation();
                m.response[0].representation[0].mediaType = presentation;
            }

            WebGetAttribute wgAttr = (WebGetAttribute)info.GetCustomAttributes(typeof(WebGetAttribute), true).FirstOrDefault();
            if (wgAttr != null)
            {
                m.name = "GET";
            }
            else
            {
                WebInvokeAttribute inAttr = (WebInvokeAttribute)info.GetCustomAttributes(typeof(WebInvokeAttribute), true).FirstOrDefault();
                if (inAttr != null)
                    m.name = inAttr.Method;

            }
            return m;
        }

        private static string GetMethodPresentation(MethodInfo info)
        {
            string presentation = "application/xml";
            string uriTemplate = GetUriTemplate(info);
            if (uriTemplate.ToLowerInvariant().Contains("=json"))
            {
                presentation = "application/json";
            }
            return presentation;
        }

        private static string GetUriTemplate(MethodInfo info)
        {
            WebGetAttribute wgattr = info.GetCustomAttributes(typeof(WebGetAttribute), true).FirstOrDefault() as WebGetAttribute;
            string uriTemplate = String.Empty;
            if (wgattr != null)
            {
                uriTemplate = wgattr.UriTemplate;
            }
            else
            {
                WebInvokeAttribute wiattr = info.GetCustomAttributes(typeof(WebInvokeAttribute), true).FirstOrDefault() as WebInvokeAttribute;
                uriTemplate = wiattr.UriTemplate;
            }
            return uriTemplate;
        }

        private void WriteToXml(string filename, application app)
        {
            using (FileStream fs = File.Create(filename))
            {
                XmlSerializer xmlser = new XmlSerializer(typeof(application));
                xmlser.Serialize(fs, app);
                fs.Close();
            }
        }

        private resources[] FindResources(Type contractTypeItem, String[] hrefs)
        {
            resources[] returnResource = new resources[1];
            returnResource[0] = new resources();
            returnResource[0].resource = new resource[1];
            returnResource[0].resource[0] = new resource();
            returnResource[0].resource[0].Items = new method[hrefs.Length];

            returnResource[0].@base = "/";

            for (int i = 0; i < hrefs.Length; i++)
            {
                method meth = new method();
                meth.href = hrefs[i];
                returnResource[0].resource[0].Items[i] = meth;
            }

            Type type = contractTypeItem.BaseType;



            if ((type == null) && (contractTypeItem.IsGenericType))
            {
                Type[] genericArgumentTypes = contractTypeItem.GetGenericArguments();
                //returnResource[0].resource[0].type = new String[genericArgumentTypes.Length];

                foreach (Type resourceType in genericArgumentTypes)
                {
                    returnResource[0].resource[0].id = resourceType.Name;
                    //returnResource[0].resource[0].type[i] = resourceType.Name;
                    //i++;
                }
            }
            else
            {
                Type[] genericArgumentTypes = type.GetGenericArguments();
                //returnResource[0].resource[0].type = new String[genericArgumentTypes.Length];
                foreach (Type resourceType in genericArgumentTypes)
                {
                    returnResource[0].resource[0].id = resourceType.Name;
                    //returnResource[0].resource[0].type[i] = resourceType.Name;
                    //i++;
                }
            }
            return returnResource;
        }

        private String[] UseSchemaSerializer(Type input)
        {
            Type[] typeArray = input.GetGenericArguments();
            XmlSchemas schemas = Hbrs.Soa.Restclient.Serializing.SchemaSerializer.WriteSchemaFromType(typeArray[0]);
            string[] returnString = new string[schemas.Count];
            for (int i = 0; i < schemas.Count; i++)
            {
                returnString[i] = (@"schema_" + i + ".xsd");
                using (TextWriter writer = File.CreateText(@"schema_" + i + ".xsd"))
                {
                    schemas[i].Write(writer);
                    writer.Close();
                }
            }
            return returnString;

        }

        private String[] methodNamesForResources(MethodInfo[] methods)
        {
            String[] returnString = new String[methods.Length];
            for (int i = 0; i < methods.Length; i++)
            {
                returnString[i] = "#" + methods[i].Name;
            }
            return returnString;
        }

        public void CreateServiceDescription(Assembly asm, string outputdir)
        {

            List<Type> contractType = GetTypesFromAssembly(asm);


            bool serviceFound = (contractType != null);
            if (serviceFound)
            {

                foreach (Type contractTypeItem in contractType)
                {
                    application app = new application();
                    app.doc = new doc[] { new doc() { title = "Auto-generated Service: " + contractTypeItem.Name } };
                    string outputFileName = contractTypeItem.Name.Replace("`", String.Empty) + ".wadl";
                    outputFileName = Path.Combine(outputdir, outputFileName);

                    string[] schemas = UseSchemaSerializer(contractTypeItem);

                    app.grammars = new grammars();
                    app.grammars.include = new include[schemas.Length];

                    for (int i = 0; i < schemas.Length; i++)
                    {
                        app.grammars.include[i] = new include();
                        app.grammars.include[i].href = schemas[i];
                    }

                    MethodInfo[] mis = contractTypeItem.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

                    var webinvokes =
                        from MethodInfo mi in mis
                        where mi.GetCustomAttributes(typeof(WebInvokeAttribute), true).Length > 0 ||
                              mi.GetCustomAttributes(typeof(WebGetAttribute), true).Length > 0
                        select mi;

                    app.Items = new object[webinvokes.Count()];
                    app.resources = FindResources(contractTypeItem, methodNamesForResources(mis));

                    if (webinvokes.Count() != 0)
                    {

                        int i = 0;
                        foreach (MethodInfo info in mis)
                        {
                            method m = FindParameters(info);
                            app.Items[i] = m;
                            i++;

                        }
                    }
                    WriteToXml(outputFileName, app);
                }
            }
        }
    }
}